/*
 * Copyright (C) 2008 Peter Kling
 * 
 * This file is part of 'A Kind of Billiard', a graphical billiard simulation.
 * 
 * 'A Kind of Billiard' is free software: you can redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * 'A Kind of Billiard' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with 'A Kind of Billiard'. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package de.akob.mathematics;

/**
 * A ray in an n-dimensional euclidean space.
 */
public class Ray {
	private MathVector origin;
	private MathVector direction;
	
	/**
	 * Creates a ray with the specified origin and direction.
	 * 
	 * @param origin origin of the ray
	 * @param direction direction of the ray
	 * @throw IllegalArgumentException if the dimensions of the given vectors do not match or if the directions norm
	 *        equals zero.
	 */
	public Ray(MathVector origin, MathVector direction) {
		if (origin.dim != direction.dim)
			throw new IllegalArgumentException("dimension of ray origin and direction must match");
		if (direction.norm() == 0.)
			throw new IllegalArgumentException("given direction must be a non-zero vector");
		this.origin = origin;
		this.direction = direction;
	}
	
	/**
	 * Return the ray point corresponding to the given ray parameter.
	 * 
	 * @param t ray parameter
	 * @return ray point corresponding to given ray parameter
	 */
	public MathVector evaluateAt(double t) {
		return Mathematics.add(origin, Mathematics.mul(t, direction));
	}
	
	/**
	 * Returns the direction of the ray.
	 * 
	 * @return direction of the ray
	 */
	public MathVector getDirection() {
		return direction;
	}
	
	/**
	 * Returns the origin of the ray.
	 * 
	 * @return origin of the ray
	 */
	public MathVector getOrigin() {
		return origin;
	}
	
	/**
	 * Gets the parameter that describes the given point on the ray using the origin and direction vectors of the ray
	 * (that is it returns t such that: point = origin + t*direction).
	 * 
	 * @param point point to get the parameter for
	 * @return the parameter t for the point if it lies on the ray; if not, NaN is returned.
	 * @throw IllegalArgumentException if the dimension of the given point does not match the rays dimension
	 */
	public double getParameter(MathVector point) {
		if (point.dim != origin.dim)
			throw new IllegalArgumentException("dimension of ray and given point must match");
		
		// check whether the given point is the origin of the ray
		MathVector toPoint = Mathematics.sub(point, origin);
		if (toPoint.norm() == 0.)
			return 0.;
		
		// check whether the given point lies on the line of the ray
		if (!Mathematics.collinear(toPoint, direction))
			return Double.NaN;
		
		// get t such that: t*direction = toPoint
		int i = 1;
		while (direction.get(i) == 0) {
			// note: direction != 0 --> at least one component != 0
			i++;
		}
		double t = toPoint.get(i) / direction.get(i);
		return t;
	}
	
	/**
	 * Checks whether the given point lies on the ray.
	 * 
	 * @param point point to check whether it lies on the ray
	 * @return whether the given point lies on the ray
	 * @throw IllegalArgumentException if the dimension of the given point does not match the rays dimension
	 */
	public boolean liesOn(MathVector point) {
		if (point.dim != origin.dim)
			throw new IllegalArgumentException("dimension of ray and given point must match");
		double t = getParameter(point);
		return (t >= 0);
	}
	
	/**
	 * Translates the ray by the given translation vector.
	 * 
	 * @param t translation vector
	 */
	public void translate(MathVector t) {
		origin.add(t);
	}
}
